package com.wyz.saas.common.gnss

import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Handler
import androidx.core.app.ActivityCompat

class AtomLocationManager : AtomLocationController {

    private var hasRunCoarseTask = false
    private var hasRunAccurateTask = false
    private var listenFine: FineLocationListener? = null
    private var listenCoarse: CoarseLocationListener? = null
    private val locMan: LocationManager
    private val ctx: Context

    private val minMeter: Int
    private val minTime: Long
    private val outTime: Long
    private val stopTime: Long

    private val listener: LocationListener
    val gnssActiveHandlers = mutableListOf<GnssActiveHandler>()

    constructor(
        ctx: Context,
        listener: LocationListener,
        minMeter: Int = 1,
        minTime: Int = 1,
        minOutTime: Int = 30,
        minStopTime: Int = -1
    ) {
        this.ctx = ctx
        this.listener = listener
        this.locMan = ctx.getSystemService(Context.LOCATION_SERVICE) as LocationManager

        this.minMeter = minMeter
        this.minTime = minTime * 1000L
        this.outTime = minOutTime * 1000L
        this.stopTime = minStopTime * 1000L
    }

    override fun request(): AtomLocationController {
        if (ActivityCompat.checkSelfPermission(
                ctx,
                Manifest.permission.ACCESS_FINE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(
                ctx,
                Manifest.permission.ACCESS_COARSE_LOCATION
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            throw Exception(" Permission is error ")
        }
        this.listenCoarse = CoarseLocationListener(listener)
        this.listenFine = FineLocationListener(listener)
        if (listenCoarse != null && listenCoarse!!.request(locMan)) listenCoarse = null
        if (listenFine != null && listenFine!!.request(locMan)) listenFine = null
        if (stopTime > 0) {
            Handler(ctx.mainLooper).postDelayed({
                if (isStart()) {
                    destroy()
                }
            }, stopTime)
        }
        return this
    }

    fun isStart(): Boolean {
        return hasRunCoarseTask || hasRunAccurateTask
    }

    override fun destroy(): AtomLocationController {
        if (listenFine != null) {
            locMan.removeUpdates(listenFine!!)
            listenFine?.onDestroy()
            listenFine = null
        }
        if (listenCoarse != null) {
            locMan.removeUpdates(listenCoarse!!)
            listenCoarse?.onDestroy()
            listenCoarse = null
        }
        this.hasRunCoarseTask = false
        this.hasRunAccurateTask = false
        this.gnssActiveHandlers.clear()
        return this
    }

    private inner class CoarseLocationListener(listener: LocationListener) :
        AbstractLocationListener(listener) {

        override fun request(manager: LocationManager): Boolean {
            return try {
                if (ActivityCompat.checkSelfPermission(
                        this@AtomLocationManager.ctx,
                        Manifest.permission.ACCESS_COARSE_LOCATION
                    ) != PackageManager.PERMISSION_GRANTED
                ) {
                    return true
                }
                manager.requestLocationUpdates(
                    LocationManager.NETWORK_PROVIDER, minTime, minMeter.toFloat(),
                    this
                )
                false
            } catch (ex: IllegalArgumentException) {
                true
            }
        }

        override fun onLocationChanged(location: Location) {
            if (!hasRunCoarseTask) {
                hasRunCoarseTask = true
            }
            gnssActiveHandlers.forEach { it.onCoarseLocationReceived() }
            val distance: Float = locationOfLastUpdate?.distanceTo(location) ?: -1f
            if (locationOfLastUpdate == null || distance >= minMeter) {
                if (listener is AtomLocationListener) {
                    listener.onLocationChanged(
                        locationOfLastUpdate,
                        location,
                        LocationManager.NETWORK_PROVIDER,
                        this@AtomLocationManager
                    )
                } else {
                    listener.onLocationChanged(location)
                }
                locationOfLastUpdate = location
            }
        }
    }

    private inner class FineLocationListener(listener: LocationListener) :
        AbstractLocationListener(listener) {

        private inner class CoarseLocationFallbackTask : Runnable {
            override fun run() {
                this@AtomLocationManager.hasRunCoarseTask = false
                this@AtomLocationManager.hasRunAccurateTask = false
                this@AtomLocationManager.listenCoarse = CoarseLocationListener(listener)
                gnssActiveHandlers.forEach { it.onCoarseRebound() }
                if (this@AtomLocationManager.listenCoarse != null
                    && this@AtomLocationManager.listenCoarse!!.request(this@AtomLocationManager.locMan)
                ) {
                    this@AtomLocationManager.listenCoarse = null
                }
            }
        }

        private var handler: Handler? = null
            get() {
                if (field == null) {
                    handler = Handler()
                }
                return field
            }

        override fun onDestroy() {
            handler?.removeCallbacksAndMessages(null)
            handler = null
            super.onDestroy()
        }

        override fun request(manager: LocationManager): Boolean {
            if (ActivityCompat.checkSelfPermission(
                    this@AtomLocationManager.ctx, Manifest.permission.ACCESS_FINE_LOCATION
                )
                != PackageManager.PERMISSION_GRANTED
            ) {
                return true
            }
            manager.requestLocationUpdates(LocationManager.GPS_PROVIDER, minTime, minMeter.toFloat(), this)
            return false
        }

        override fun onLocationChanged(location: Location) {
            if (listenCoarse != null) {
                locMan.removeUpdates(listenCoarse!!)
                listenCoarse = null
            }
            if (!hasRunAccurateTask) {
                hasRunAccurateTask = true
            }
            gnssActiveHandlers.forEach {
                it.onAccurateLocationReceived()
            }

            handler?.removeCallbacksAndMessages(null)
            handler?.postDelayed(CoarseLocationFallbackTask(), outTime)

            val distance: Float = locationOfLastUpdate?.distanceTo(location) ?: -1f
            if (locationOfLastUpdate == null || distance >= minMeter) {
                if (listener is AtomLocationListener) {
                    listener.onLocationChanged(
                        locationOfLastUpdate,
                        location,
                        LocationManager.GPS_PROVIDER,
                        this@AtomLocationManager
                    )
                } else {
                    listener.onLocationChanged(location)
                }
                locationOfLastUpdate = location
            }
        }
    }

    abstract class AbstractLocationListener(protected val listener: LocationListener) :
        LocationListener {

        protected var locationOfLastUpdate: Location? = null

        override fun onProviderEnabled(provider: String) {
            listener.onProviderEnabled(provider)
        }

        override fun onProviderDisabled(provider: String) {
            listener.onProviderDisabled(provider)
        }

        @Throws(SecurityException::class)
        abstract fun request(manager: LocationManager): Boolean

        open fun onDestroy() {
            locationOfLastUpdate = null
        }
    }

    interface GnssActiveHandler {
        fun onCoarseLocationReceived()
        fun onAccurateLocationReceived()
        fun onCoarseRebound()
    }
}